#region Using References

using System;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

#endregion

namespace Beaker.Specs
{
	/// <summary>
	/// Base class for a unit test that test a subject of the given type
	/// </summary>
	/// <typeparam name="TSubject">The type of the subject to test.</typeparam>
	/// <typeparam name="TSubjectBuilder">The type of the builder that builds the subject.</typeparam>
	[TestClass]
	public abstract class SpecsFor<TSubject, TSubjectBuilder>
		where TSubjectBuilder : IBuilder<TSubject>, new()
	{
		private readonly Some _some = new Some();
		private TSubjectBuilder _builder;
		private bool _isBuilding;
		private TSubject _subject;

		[TestInitialize]
		public void BaseTestInitialize()
		{
			_builder = new TSubjectBuilder();
			_isBuilding = true;
		}

		public TestContext TestContext { get; set; }

		[TestCleanup]
		public void BaseTestCleanup()
		{
			if (TestContext.CurrentTestOutcome == UnitTestOutcome.Passed)
				_isBuilding.Should().BeFalse("Test should not exit while still in building/arrange phase");
		}

		/// <summary>
		/// Gets the builder for the subject under test
		/// </summary>
		protected TSubjectBuilder BuildSubject
		{
			get
			{
				if (!_isBuilding)
					throw new InvalidOperationException("Cannot access builder anymore when in act/assert state");

				return _builder;
			}
		}

		/// <summary>
		/// Creates another - new - builder for the same type as the subject for tests that require two instance of the subject.
		/// </summary>
		/// <returns>A new builder for the same type as the subject.</returns>
		protected TSubjectBuilder BuildAnother()
		{
			return new TSubjectBuilder();
		}

		/// <summary>
		/// Build the subject from the subject builder
		/// </summary>
		/// <returns></returns>
		protected TSubject Build()
		{
			return Subject;
		}

		/// <summary>
		/// Get an action that will call the builder, for testing when building should throw
		/// </summary>
		protected Action Building
		{
			get { return () => Build(); }
		}

		/// <summary>
		/// Gets the subject under test
		/// </summary>
		protected TSubject Subject
		{
			get
			{
				if (_isBuilding)
				{
					_isBuilding = false;

					OnPreBuild();
					_subject = _builder.Build();
					OnPostBuild(_subject);
				}

				return _subject;
			}
		}

		protected virtual void OnPreBuild()
		{
		}

		protected virtual void OnPostBuild(TSubject subject)
		{
		}

		public Some Some
		{
			get { return _some; }
		}
	}

	public abstract class SpecsFor<TSubject>
		: SpecsFor<TSubject, SubjectBuilder<TSubject>>
		where TSubject : new()
	{
	}

	public sealed class SubjectBuilder<TSubject>
		: Builder<TSubject>
		where TSubject : new()
	{
		public override TSubject Build()
		{
			return new TSubject();
		}
	}
}